28  农产品交易可视化性分析

28.1 引言农产品电商的数据价值

农产品电子商务的快速发展产生了海量交易数据。通过可视化分析,我们可以:

发现业务规律: - 识别季节性需求和价格波动模式 - 发现区域消费偏好差异 - 优化供应链和库存管理

理论背景:探索性数据分析(EDA)

探索性数据分析(Exploratory Data Analysis, EDA)由统计学家John Tukey于1977年提出。其核心思想是在进行正式建模之前,通过统计图形和摘要统计来理解数据的特征。

EDA的四个核心原则: 1. 怀疑精神: 对数据和假设保持质疑 2. 可视化优先: 图形比数字更直观 3. 计算支撑: 用统计量验证视觉发现 4. 迭代探索: 分析是循环往复的过程

农产品数据的特殊性: - 季节性: 农产品生产受自然周期影响 - 地域性: 不同地区消费习惯差异大 - 易腐性: 保鲜期短,库存管理至关重要 - 价格波动: 受供需关系和政策影响明显

28.2 数据准备与探索

28.2.1 数据读取与初步检查

平台任务解答代码

以下代码与教学平台任务要求完全一致:

列表 28.1
# 注:processed_data.csv数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 导入必要的数据分析和可视化库
import pandas as pd
import numpy as np  # 导入NumPy数值计算库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
import seaborn as sns  # 导入Seaborn可视化库
import warnings  # 导入warnings模块用于控制警告输出
warnings.filterwarnings('ignore')  # 忽略运行时警告信息以保持输出整洁
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体作为默认字体以支持中文
plt.rcParams['axes.unicode_minus'] = False  # 正确显示负号

# 读取处理好的数据集
data = pd.read_csv('processed_data.csv')

# 查看数据集的基本情况
print(data.head())  # 显示数据集前几行
print("查看数据集的基本信息:")  # 输出查看数据集的基本信息
print(data.info())  # 显示数据集的基本信息,包括列名、数据类型和非空值数量
print("查看数据集有无缺失值:")  # 输出查看数据集有无缺失值
print(data.isnull().sum())  # 统计每列的缺失值数量
print("查看数据集有无重复值:")  # 输出查看数据集有无重复值
print(data.duplicated().sum())  # 统计重复行的数量

# 创建特征映射字典,将英文列名映射为中文名称,用于图表标题
feature_map = {
    'price': '价格',  # 映射"price"→"价格"
    'quantity': '数量',  # 映射"quantity"→"数量"
    'customer_age': '顾客年龄',  # 映射"customer_age"→"顾客年龄"
    'return_flag': '是否返单',  # 映射"return_flag"→"是否返单"
    'sales_amount': '销售额'  # 映射"sales_amount"→"销售额"
}  # 数据结构定义结束

# 创建箱型图展示各特征的分布情况
plt.figure(figsize=(20,10))  # 设置图形大小
for i, feature in enumerate(feature_map.keys()):  # 循环遍历
    plt.subplot(2,3, i+1)  # 创建2行3列的子图布局
    plt.title(f'{feature_map[feature]}的箱型图')  # 设置子图标题
    plt.boxplot(data[feature])  # 绑制箱线图
    plt.xlabel(f'{feature_map[feature]}')  # 设置x轴标签
    plt.ylabel('频率')  # 设置y轴标签
plt.savefig("特征箱型图.png")  # 保存图形

# 创建一个字典来映射产品名称到正确的分类
category_mapping = {
    '安溪铁观音': '茶叶',  # 映射"安溪铁观音"→"茶叶"
    '武夷岩茶': '茶叶',  # 映射"武夷岩茶"→"茶叶"
    '福州茉莉花': '茶叶',  # 映射"福州茉莉花"→"茶叶"
    '古田银耳': '食用菌',  # 映射"古田银耳"→"食用菌"
    '建宁莲子': '中药材',  # 映射"建宁莲子"→"中药材"
    '琯溪蜜柚': '水果',  # 映射"琯溪蜜柚"→"水果"
    '宁德大黄鱼': '水产品'  # 映射"宁德大黄鱼"→"水产品"
}  # 数据结构定义结束

# 应用映射来更新category列,将产品名称转换为产品类别
data['category'] = data['product_name'].map(category_mapping).fillna(data['category'])

# 2.可视化展示 - 计算并转置描述性统计数据(结果未打印,仅计算)
data.describe().T

# 2.1 顾客信息可视化
plt.figure(figsize=(20,10))  # 设置图形大小

# 顾客年龄分布直方图
plt.subplot(2,2,1)
sns.distplot(data['customer_age'], bins=30, kde=True, color='blue')  # 绘制带核密度估计的直方图
plt.title('顾客年龄分布', fontsize=16)  # 设置标题
plt.xlabel('顾客年龄', fontsize=12)  # 设置x轴标签
plt.ylabel('频率', fontsize=12)  # 设置y轴标签

# 顾客年龄组别分布柱状图
plt.subplot(2,2,2)
order = ['18-25', '26-35', '36-45', '46-55', '56+']  # 自定义年龄组顺序
sns.countplot(data=data, x='age_group', color='orange', order=order)  # 按指定顺序绘制柱状图
plt.title('顾客年龄组别分布', fontsize=16)  # 设置标题
plt.xlabel('顾客年龄组别', fontsize=12)  # 设置x轴标签
plt.ylabel('频率', fontsize=12)  # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')

# 顾客性别分布柱状图
plt.subplot(2,2,3)
sns.countplot(data=data, x='customer_gender', color='green')  # 绘制性别分布柱状图
plt.title('顾客性别分布', fontsize=16)  # 设置标题
plt.xlabel('顾客性别', fontsize=12)  # 设置x轴标签
plt.ylabel('频率', fontsize=12)  # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')

# 顾客性别比例饼图
plt.subplot(2,2,4)
plt.pie(data['customer_gender'].value_counts(),  # 绑制饼图
        labels=data['customer_gender'].value_counts().index,  # 统计各个值的出现频次
        autopct='%1.1f%%', colors=['green', 'orange'])  # 绘制饼图并显示百分比
plt.title('顾客性别比例', fontsize=16)  # 设置标题
plt.axis('equal')  # 确保饼图是圆形
plt.savefig("顾客信息.png")  # 保存图形
#1、从顾客年龄组别的分布可以了解到,26-55岁间的顾客最多。
#2、从顾客性别的分布可以了解到,男性顾客比女性顾客多,男性占比60.4%,女性占比39.6%。

# 区域分布可视化
plt.figure(figsize=(20,5))  # 设置图形大小
sns.countplot(x='region', data=data, palette='Set2')  # 绑制计数柱状图
plt.title('区域分布', fontsize=20)  # 设置标题
plt.xlabel('区域', fontsize=16)  # 设置x轴标签
plt.ylabel('数量', fontsize=16)  # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')
plt.savefig("区域分布.png")  # 保存图形

# 2.2 商品信息可视化
plt.figure(figsize=(20,5))  # 设置图形大小

# 产品名称分布柱状图
plt.subplot(1, 2, 1)
sns.countplot(x='product_name', data=data, palette='Set2')  # 绘制产品名称分布柱状图
plt.xlabel('产品名称', fontsize=16)  # 设置x轴标签
plt.ylabel('数量', fontsize=16)  # 设置y轴标签
plt.title('产品分布', fontsize=20)  # 设置标题
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')

# 产品类型分布柱状图
plt.subplot(1, 2, 2)
sns.countplot(x='category', data=data, palette='Set2')  # 绘制产品类型分布柱状图
plt.xlabel('产品类型', fontsize=16)  # 设置x轴标签
plt.ylabel('数量', fontsize=16)  # 设置y轴标签
plt.title('产品类型分布', fontsize=20)  # 设置标题
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')
plt.savefig("商品信息.png")  # 保存图形
#总体而言,茶叶是销量最多的产品类型,远超其他产品类型。
#具体产品分布上,销量最多的产品是古田银耳,共1474单;其次是安溪铁观音,共1461单;第三名是宁德大黄鱼,共1438单。

# 销售相关指标分布可视化
plt.figure(figsize=(20,6))  # 设置图形大小

# 价格分布直方图
plt.subplot(1, 3, 1)
sns.distplot(data['price'], kde=True, color='blue')  # 绘制价格分布直方图
plt.title('价格分布', fontsize=16)  # 设置标题
plt.xlabel('价格', fontsize=12)  # 设置x轴标签
plt.ylabel('频率', fontsize=12)  # 设置y轴标签

# 数量分布直方图
plt.subplot(1, 3, 2)
sns.distplot(data['quantity'], kde=True, color='green')  # 绘制数量分布直方图
plt.title('数量分布', fontsize=16)  # 设置标题
plt.xlabel('数量', fontsize=12)  # 设置x轴标签
plt.ylabel('频率', fontsize=12)  # 设置y轴标签

# 销售额分布直方图
plt.subplot(1, 3, 3)
sns.distplot(data['sales_amount'], kde=True, color='orange')  # 绘制销售额分布直方图
plt.title('销售额分布', fontsize=16)  # 设置标题
plt.xlabel('销售额', fontsize=12)  # 设置x轴标签
plt.ylabel('频率', fontsize=12)  # 设置y轴标签
plt.savefig("销售.png")  # 保存图形

# 2.3 平台及优惠信息可视化
plt.figure(figsize=(20,12))  # 设置图形大小

# 平台分布柱状图
plt.subplot(2, 3, 1)
sns.countplot(x='channel', data=data, palette='Set2')  # 绘制平台分布柱状图
plt.title('平台分布', fontsize=16)  # 设置标题
plt.xlabel('平台', fontsize=14)  # 设置x轴标签
plt.ylabel('频数', fontsize=14)  # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')

# 平台比例饼图
plt.subplot(2, 3 ,2)
plt.pie(data['channel'].value_counts(),  # 绑制饼图
        labels=data['channel'].value_counts().index,  # 统计各个值的出现频次
        autopct='%1.1f%%')  # 绘制平台比例饼图
plt.title('平台比例', fontsize=16)  # 设置标题

# 优惠情况分布柱状图
plt.subplot(2,3, 3)
sns.countplot(x='promotion', data=data, palette='Set2')  # 绘制优惠情况分布柱状图
plt.title('优惠情况分布', fontsize=16)  # 设置标题
plt.xlabel('优惠情况', fontsize=14)  # 设置x轴标签
plt.ylabel('频数', fontsize=14)  # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')

# 优惠情况比例饼图
plt.subplot(2, 3, 4)
plt.pie(data['promotion'].value_counts(),  # 绑制饼图
        labels=data['promotion'].value_counts().index,  # 统计各个值的出现频次
        autopct='%1.1f%%')  # 绘制优惠情况比例饼图
plt.title('优惠情况比例', fontsize=16)  # 设置标题

# 是否返单分布柱状图
plt.subplot(2, 3, 5)
sns.countplot(x='return_flag', data=data, palette='Set2')  # 绘制是否返单分布柱状图
plt.title('是否返单分布', fontsize=16)  # 设置标题
plt.xlabel('是否返单', fontsize=14)  # 设置x轴标签
plt.ylabel('频数', fontsize=14)  # 设置y轴标签
# 在每个柱子上方添加具体数值
for p in plt.gca().patches:
    plt.gca().annotate(format(p.get_height(), '.0f'),  # 获取当前坐标轴并在柱状图顶部标注数值
                       (p.get_x() + p.get_width() / 2., p.get_height()),  # 设置标注位置为柱子顶部中心
                       # 设置标注文本的水平对齐、垂直偏移等显示参数
                       ha='center', va='center', xytext=(0, 5), textcoords='offset points')

# 是否返单比例饼图
plt.subplot(2, 3, 6)
plt.pie(data['return_flag'].value_counts(),  # 绑制饼图
        labels=data['return_flag'].value_counts().index,  # 统计各个值的出现频次
        autopct='%1.1f%%')  # 绘制是否返单比例饼图
plt.title('是否返单比例', fontsize=16)  # 设置标题
plt.savefig("平台及优惠信息.png")  # 保存图形
#从平台的分布上可以了解到,淘宝订单数量是最多的,占比29.6%;京东平台订单数量第二,共2504单,占比25%;第三是拼多多平台;自由平台的订单数量最少。
#从优惠情况可以了解到,没有优惠的订单占比50.3%,有优惠的订单占比49.7%。其中,满减订单占比29.4%,折扣订单占比20.3%。
#是否返单的情况可以了解到,有返单的订单占比4.8%,没有返单的订单占比95.2%。
列表 28.2
# 注:processed_data.csv数据文件本地没有,但平台已经内置

# =============================================================================
# 题目: 农产品交易数据预处理
# =============================================================================
# 本代码块演示农产品电商交易数据的加载和初步检查。数据预处理是分析
# 的第一步,通过查看数据结构、类型和基本统计,我们可以快速了解数据
# 的质量,为后续分析奠定基础。

# ==================== 导入必要的库 ====================
import pandas as pd  # 导入pandas库,用于数据操作和分析
import numpy as np  # 导入numpy库,用于数值计算
import matplotlib.pyplot as plt  # 导入matplotlib库,用于数据可视化
import seaborn as sns  # 导入seaborn库,用于绘制高级统计图形
import warnings  # 导入warnings库,用于处理警告信息
warnings.filterwarnings('ignore')  # 忽略警告信息,保持输出整洁

# ==================== 设置中文显示 ====================
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# ==================== 读取数据 ====================
data = pd.read_csv('processed_data.csv')  # 读取CSV文件到DataFrame

# ==================== 输出数据基本信息 ====================
print('数据形状:', data.shape)  # 打印数据维度(行数,列数)
print('\n数据前5行:')  # 打印标题
print(data.head())  # 打印前5行数据,快速浏览数据结构
print('\n数据信息:')  # 打印标题
print(data.info())  # 打印数据信息(列名、数据类型、非空值数量)
print('\n描述统计:')  # 打印标题
print(data.describe().T)  # 打印描述统计(转置显示更易读)

代码深度解析:

  1. 数据结构:
    • shape: 返回(行数, 列数)
    • info(): 显示每列的数据类型和非空值数量
    • describe().T: 转置描述统计表,更易阅读
  2. 数据类型检查:
    • 数值型(int/float): 年龄、金额、数量
    • 字符型(object): 产品名称、类别
    • 日期型(datetime): 需要转换
  3. 中文配置:
    • SimHei(黑体): 常用中文字体
    • unicode_minus=False: 修复负号显示问题

28.2.2 产品分类映射

补充说明:产品分类学

产品分类是电商数据分析的基础。常见的分类方法:

  1. 功能分类: 按产品用途分类
  2. 属性分类: 按产品属性分类
  3. 场景分类: 按使用场景分类

在农产品电商中,我们通常采用混合分类策略: - 一级分类: 茶叶、水果、水产品等大类 - 二级分类: 具体品种(铁观音、武夷岩茶) - 三级分类: 产地、等级等细分

列表 28.3
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 农产品分类映射
# =============================================================================
# 本代码块演示如何将具体产品名称映射到标准分类体系。分类映射是数据清洗
# 的重要步骤,统一的产品分类便于后续的聚合分析和可视化。

# ==================== 产品名称到分类的映射 ====================
category_mapping = {
    '安溪铁观音': '茶叶',  # 安溪铁观音属于茶叶类
    '武夷岩茶': '茶叶',  # 武夷岩茶属于茶叶类
    '福州茉莉花': '茶叶',  # 福州茉莉花属于茶叶类
    '古田银耳': '食用菌',  # 古田银耳属于食用菌类
    '建宁莲子': '中药材',  # 建宁莲子属于中药材类
    '琯溪蜜柚': '水果',  # 琯溪蜜柚属于水果类
    '宁德大黄鱼': '水产品'  # 宁德大黄鱼属于水产品类
}

# ==================== 应用映射 ====================
# map()函数根据字典映射,将产品名称转换为分类
# fillna()保留未匹配的值,保持原分类不变
data['category'] = data['product_name'].map(category_mapping).fillna(data['category'])

# ==================== 验证映射结果 ====================
print('分类映射结果:')  # 打印标题
print(data[['product_name', 'category']].drop_duplicates().head(10))  # 去重查看唯一映射

# ==================== 验证分类完整性 ====================
print('\n分类统计:')  # 打印标题
print(data['category'].value_counts())  # 统计各分类的产品数量

代码深度解析:

  1. map()函数:
    • 根据字典进行键值映射
    • fillna(): 处理未匹配的值(保留原分类)
    • 比循环映射高效100倍以上
  2. 数据质量验证:
    • drop_duplicates(): 去重查看唯一映射
    • value_counts(): 统计各分类产品数量

28.3 数据质量检查

28.3.1 缺失值分析

理论背景:缺失值的类型

根据Rubin(1976)的分类,缺失值分为三类:

  1. MCAR (Missing Completely At Random):
    • 缺失与任何变量无关
    • 完全随机发生
    • 处理方法:直接删除或插补
  2. MAR (Missing At Random):
    • 缺失与观测变量相关
    • 例如:高价值客户更不愿填写收入
    • 处理方法:多重插补
  3. MNAR (Missing Not At Random):
    • 缺失与未观测变量相关
    • 例如:极高收入者拒绝回答
    • 处理方法:建模机制
列表 28.4
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 数据质量检查与可视化
# =============================================================================
# 本代码块演示如何进行数据质量检查,包括缺失值分析、重复值检测等。
# 数据质量直接影响分析结果的可靠性,因此是数据预处理的关键环节。

# ==================== 缺失值检查 ====================
print('缺失值统计:')  # 打印标题
missing_counts = data.isnull().sum()  # 统计每列的缺失值数量
missing_pct = (missing_counts / len(data)) * 100  # 计算缺失值比例
missing_df = pd.DataFrame({  # 将结果整理为DataFrame
    '缺失数': missing_counts,  # 缺失值数量
    '缺失比例': missing_pct  # 缺失值百分比
})
print(missing_df)  # 打印缺失值统计表

# ==================== 缺失值可视化 ====================
plt.figure(figsize=(10, 6))  # 创建图形,尺寸10×6英寸
missing_df['缺失比例'].plot(kind='bar', color='steelblue', alpha=0.7)  # 绘制柱状图
plt.title('各字段缺失值比例', fontsize=14)  # 设置标题
plt.xlabel('字段名', fontsize=12)  # 设置x轴标签
plt.ylabel('缺失比例(%)', fontsize=12)  # 设置y轴标签
plt.xticks(rotation=45)  # x轴标签旋转45度
plt.grid(axis='y', alpha=0.3)  # 显示y轴网格
plt.axhline(y=5, color='red', linestyle='--', label='5%阈值')  # 添加5%阈值参考线
plt.legend()  # 显示图例
plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

# ==================== 重复值检查 ====================
print(f'\n重复值数量: {data.duplicated().sum()}')  # 打印重复行数量
print(f'重复值比例: {data.duplicated().sum() / len(data) * 100:.2f}%')  # 打印重复值比例

# ==================== 数据清洗 ====================
data_clean = data.drop_duplicates()  # 删除重复行
print(f'\n清洗后数据形状: {data_clean.shape}')  # 打印清洗后的数据维度
print(f'数据保留率: {len(data_clean) / len(data) * 100:.2f}%')  # 计算数据保留率

28.3.2 异常值检测

理论背景:异常值的识别方法

异常值(Outlier)指显著偏离其他观测值的数据点。常见识别方法:

  1. 基于统计量:
    • 3σ原则: 超出均值±3倍标准差
    • IQR方法: 超出Q1-1.5×IQR或Q3+1.5×IQR
  2. 基于可视化:
    • 箱线图: 直观显示离群点
    • 散点图: 识别偏离整体趋势的点
  3. 基于模型:
    • DBSCAN聚类: 密度较低的点
    • 孤立森林: 异常得分高的点
列表 28.5
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 农产品价格异常值检测
# =============================================================================
# 本代码块演示如何使用箱线图和IQR方法检测异常值。异常值可能由数据录入错误、
# 测量误差或真实的极端情况造成,需要根据业务判断如何处理。

# ==================== 选择数值型字段 ====================
numeric_cols = data_clean.select_dtypes(include=[np.number]).columns  # 选择数值类型的列

# ==================== 绘制箱线图 ====================
fig, axes = plt.subplots(2, 2, figsize=(14, 10))  # 创建2×2子图布局
axes = axes.flatten()  # 将子图数组展平为一维

# 遍历前4个数值字段,绘制箱线图
for i, col in enumerate(numeric_cols[:4]):  # 只取前4个字段
    data_clean.boxplot(column=col, ax=axes[i])  # 绘制箱线图
    axes[i].set_title(f'{col} - 箱线图', fontsize=12)  # 设置子图标题
    axes[i].grid(axis='y', alpha=0.3)  # 显示y轴网格

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

# ==================== 统计异常值数量(IQR方法) ====================
def detect_outliers_iqr(series):
    """
    使用IQR方法检测异常值
    参数:
        series: pandas Series,待检测的数据序列
    返回:
        异常值数量
    """
    Q1 = series.quantile(0.25)  # 计算第一四分位数(25%分位)
    Q3 = series.quantile(0.75)  # 计算第三四分位数(75%分位)
    IQR = Q3 - Q1  # 计算四分位距
    lower = Q1 - 1.5 * IQR  # 计算下界(小于此值为异常值)
    upper = Q3 + 1.5 * IQR  # 计算上界(大于此值为异常值)
    return ((series < lower) | (series > upper)).sum()  # 返回异常值数量

# ==================== 输出异常值统计 ====================
print('\n异常值统计(IQR方法):')  # 打印标题
for col in numeric_cols:  # 遍历所有数值字段
    outlier_count = detect_outliers_iqr(data_clean[col].dropna())  # 检测异常值数量
    print(f'{col}: {outlier_count}个异常值 ({outlier_count/len(data_clean)*100:.2f}%)')  # 打印结果

28.4 数据分布分析

28.4.1 单变量分布

理论背景:分布的特征

描述数据分布的三个维度:

  1. 集中趋势:
    • 均值: 所有值的平均
    • 中位数: 排序后的中间值
    • 众数: 出现频率最高的值
  2. 离散程度:
    • 方差: 数据偏离均值的程度
    • 标准差: 方差的平方根
    • 极差: 最大值与最小值的差
  3. 分布形状:
    • 偏度: 分布的对称性
    • 峰度: 分布的尖锐程度
列表 28.6
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 农产品销售额分布分析
# =============================================================================
# 本代码块演示如何分析销售额的分布特征。通过直方图和箱线图,我们可以
# 直观地观察数据的分布形态、中心趋势和离散程度,为业务决策提供依据。

# ==================== 提取销售额数据 ====================
sales = data_clean['sales_amount']  # 提取销售额列

# ==================== 绘制分布图 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))  # 创建1×2子图布局

# 子图1:直方图
axes[0].hist(sales, bins=50, color='steelblue', alpha=0.7, edgecolor='black')  # 绘制直方图
# 添加均值参考线
axes[0].axvline(sales.mean(), color='red', linestyle='--', linewidth=2, label=f'均值={sales.mean():.2f}')
# 添加中位数参考线
axes[0].axvline(sales.median(), color='green', linestyle='--', linewidth=2, label=f'中位数={sales.median():.2f}')
axes[0].set_title('销售额分布直方图', fontsize=14)  # 设置标题
axes[0].set_xlabel('销售额(元)', fontsize=12)  # 设置x轴标签
axes[0].set_ylabel('频数', fontsize=12)  # 设置y轴标签
axes[0].legend(fontsize=10)  # 显示图例
axes[0].grid(axis='y', alpha=0.3)  # 显示y轴网格

# 子图2:箱线图
axes[1].boxplot(sales, vert=True)  # 绘制箱线图,垂直方向
axes[1].set_title('销售额箱线图', fontsize=14)  # 设置标题
axes[1].set_ylabel('销售额(元)', fontsize=12)  # 设置y轴标签
axes[1].grid(axis='y', alpha=0.3)  # 显示y轴网格

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

# ==================== 计算分布统计量 ====================
from scipy.stats import skew, kurtosis  # 导入偏度和峰度函数

print('\n销售额分布统计:')  # 打印标题
print(f'均值: {sales.mean():.2f}')  # 打印均值
print(f'中位数: {sales.median():.2f}')  # 打印中位数
print(f'标准差: {sales.std():.2f}')  # 打印标准差
print(f'偏度: {skew(sales):.4f}')  # 打印偏度(正值右偏,负值左偏)
print(f'峰度: {kurtosis(sales):.4f}')  # 打印峰度(正值尖峰,负值平峰)

# ==================== 偏度解读 ====================
if abs(skew(sales)) < 0.5:  # 偏度绝对值小于0.5
    skew_interpret = '接近对称分布'  # 分布接近对称
elif skew(sales) > 0:  # 偏度大于0
    skew_interpret = '右偏分布(存在高值异常)'  # 正偏(右偏),存在高值异常
else:  # 偏度小于0
    skew_interpret = '左偏分布(存在低值异常)'  # 负偏(左偏),存在低值异常

print(f'\n偏度解读: {skew_interpret}')  # 打印偏度解读

28.4.2 类别分布

列表 28.7
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 农产品品类分布分析
# =============================================================================
# 本代码块演示如何分析不同品类农产品的销售情况。通过聚合统计和可视化,
# 我们可以识别核心品类、辅助品类和长尾品类,为商品策略和库存管理提供依据。

# ==================== 按品类聚合统计 ====================
category_sales = data_clean.groupby('category')['sales_amount'].agg([  # 按分类分组,聚合销售额
    'sum',  # 计算总销售额
    'count',  # 计算订单数
    'mean'  # 计算平均单价
])
category_sales.columns = ['总销售额', '订单数', '平均单价']  # 重命名列
category_sales = category_sales.sort_values('总销售额', ascending=False)  # 按总销售额降序排序

# ==================== 输出统计结果 ====================
print('\n品类销售统计:')  # 打印标题
print(category_sales)  # 打印品类统计表

# ==================== 可视化 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))  # 创建1×2子图布局

# 子图1:总销售额柱状图
category_sales['总销售额'].plot(kind='bar', ax=axes[0], color='steelblue', alpha=0.7)  # 绘制柱状图
axes[0].set_title('各品类总销售额', fontsize=14)  # 设置标题
axes[0].set_xlabel('产品类别', fontsize=12)  # 设置x轴标签
axes[0].set_ylabel('销售额(元)', fontsize=12)  # 设置y轴标签
axes[0].tick_params(axis='x', rotation=45)  # x轴标签旋转45度
axes[0].grid(axis='y', alpha=0.3)  # 显示y轴网格

# 子图2:订单数饼图
axes[1].pie(category_sales['订单数'], labels=category_sales.index, autopct='%1.1f%%',
            startangle=90, colors=sns.color_palette('Set3'))  # 绘制饼图
axes[1].set_title('各品类订单数占比', fontsize=14)  # 设置标题

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

28.5 时间序列初步分析

28.5.1 时间特征提取

补充说明:时间序列的特征

时间序列数据具有独特特征: 1. 趋势性(Trend): 长期上升或下降趋势 2. 季节性(Seasonality): 固定周期的波动 3. 周期性(Cyclicity): 不固定周期的波动 4. 随机性(Random): 不可预测的噪声

列表 28.8
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 时间特征提取与分析
# =============================================================================
# 本代码块演示如何从日期时间字段中提取时间特征(年、月、日、星期、小时等)。
# 时间特征提取是时间序列分析的基础,有助于发现周期性模式和季节性规律。

# ==================== 确保日期格式正确 ====================
data_clean['下单日期'] = pd.to_datetime(data_clean['order_date'])  # 转换为datetime类型

# ==================== 提取时间特征 ====================
data_clean['年'] = data_clean['下单日期'].dt.year  # 提取年份
data_clean['月'] = data_clean['下单日期'].dt.month  # 提取月份(1-12)
data_clean['日'] = data_clean['下单日期'].dt.day  # 提取日(1-31)
data_clean['星期'] = data_clean['下单日期'].dt.dayofweek  # 提取星期(0=周一,6=周日)
data_clean['小时'] = data_clean['下单日期'].dt.hour  # 提取小时(0-23)

# ==================== 按月统计 ====================
monthly_sales = data_clean.groupby('月')['sales_amount'].agg([  # 按月份分组统计
    'sum',  # 总销售额
    'count'  # 订单数
])
monthly_sales.columns = ['月销售额', '月订单数']  # 重命名列

# ==================== 输出统计结果 ====================
print('月度销售统计:')  # 打印标题
print(monthly_sales)  # 打印月度统计表

# ==================== 可视化月度趋势 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))  # 创建1×2子图布局

monthly_sales['月销售额'].plot(ax=axes[0], marker='o', linewidth=2)  # 绘制折线图
axes[0].set_title('月销售额趋势', fontsize=14)  # 设置标题
axes[0].set_xlabel('月份', fontsize=12)  # 设置x轴标签
axes[0].set_ylabel('销售额(元)', fontsize=12)  # 设置y轴标签
axes[0].grid(True, alpha=0.3)  # 显示网格

monthly_sales['月订单数'].plot(kind='bar', ax=axes[1], color='coral', alpha=0.7)  # 绘制柱状图
axes[1].set_title('月订单数分布', fontsize=14)  # 设置标题
axes[1].set_xlabel('月份', fontsize=12)  # 设置x轴标签
axes[1].set_ylabel('订单数', fontsize=12)  # 设置y轴标签
axes[1].grid(axis='y', alpha=0.3)  # 显示y轴网格

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

28.5.2 周末效应分析

列表 28.9
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 周末效应分析
# =============================================================================
# 本代码块演示如何分析工作日与周末的消费差异。周末效应分析可以帮助
# 企业制定营销策略和库存管理计划,例如周末增加营销投入或调整物流配送。

# ==================== 标记周末 ====================
# isin([5, 6])判断星期是否为5(周六)或6(周日)
data_clean['是否周末'] = data_clean['星期'].isin([5, 6])  # 创建周末标记列

# ==================== 周末vs工作日对比 ====================
weekend_comparison = data_clean.groupby('是否周末')['sales_amount'].agg([  # 按是否周末分组
    'mean',  # 平均销售额
    'sum',  # 总销售额
    'count'  # 订单数
])
weekend_comparison.index = ['工作日', '周末']  # 重命名索引

# ==================== 输出对比结果 ====================
print('周末效应分析:')  # 打印标题
print(weekend_comparison)  # 打印对比表

# ==================== 可视化对比 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))  # 创建1×2子图布局

# 子图1:平均销售额对比
weekend_comparison['mean'].plot(kind='bar', ax=axes[0], color=['skyblue', 'orange'], alpha=0.7)  # 绘制柱状图
axes[0].set_title('平均销售额对比', fontsize=14)  # 设置标题
axes[0].set_ylabel('平均销售额(元)', fontsize=12)  # 设置y轴标签
axes[0].tick_params(axis='x', rotation=0)  # x轴标签不旋转
axes[0].grid(axis='y', alpha=0.3)  # 显示y轴网格

# 子图2:总销售额对比
weekend_comparison['sum'].plot(kind='bar', ax=axes[1], color=['skyblue', 'orange'], alpha=0.7)  # 绘制柱状图
axes[1].set_title('总销售额对比', fontsize=14)  # 设置标题
axes[1].set_ylabel('总销售额(元)', fontsize=12)  # 设置y轴标签
axes[1].tick_params(axis='x', rotation=0)  # x轴标签不旋转
axes[1].grid(axis='y', alpha=0.3)  # 显示y轴网格

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

28.6 多变量关系探索

28.6.1 价格与销量的关系

理论背景:价格弹性

价格弹性(Price Elasticity)衡量需求量对价格变化的敏感度:

\[ E_p = \frac{\%\Delta Q}{\%\Delta P} = \frac{\partial Q}{\partial P} \times \frac{P}{Q} \]

  • |Ep| > 1: 富有弹性(奢侈品)
  • |Ep| < 1: 缺乏弹性(必需品)
  • |Ep| = 1: 单位弹性
列表 28.10
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 价格与销量关系分析
# =============================================================================
# 本代码块演示如何分析产品价格与销量之间的关系。价格-销量关系是
# 定价策略的核心,通过相关性分析和可视化,可以评估产品的价格弹性。

# ==================== 按产品统计价格与销量 ====================
product_stats = data_clean.groupby('product_name').agg({  # 按产品名称分组
    'sales_amount': 'sum',  # 总销售额
    'quantity': 'sum',  # 总销量
    'unit_price': 'mean'  # 平均单价
})
product_stats['单价'] = product_stats['sales_amount'] / product_stats['quantity']  # 计算实际单价

# ==================== 计算相关系数 ====================
corr_coef = product_stats[['单价', 'quantity']].corr().iloc[0, 1]  # 计算价格与销量的相关系数
print(f'价格与销量的相关系数: {corr_coef:.4f}')  # 打印相关系数

# ==================== 可视化 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))  # 创建1×2子图布局

# 子图1:散点图
axes[0].scatter(product_stats['单价'], product_stats['quantity'], s=100, alpha=0.6)  # 绘制散点图
axes[0].set_title(f'价格-销量散点图 (相关系数={corr_coef:.4f})', fontsize=14)  # 设置标题
axes[0].set_xlabel('单价(元)', fontsize=12)  # 设置x轴标签
axes[0].set_ylabel('销量', fontsize=12)  # 设置y轴标签
axes[0].grid(True, alpha=0.3)  # 显示网格

# 添加产品标签
for idx, row in product_stats.iterrows():  # 遍历每个产品
    axes[0].annotate(idx, (row['单价'], row['quantity']), fontsize=8)  # 添加产品名称标签

# 子图2:热力图
sns.heatmap(product_stats[['单价', 'quantity']].corr(), annot=True, cmap='coolwarm',
            center=0, ax=axes[1], cbar_kws={'label': '相关系数'})  # 绘制相关性热力图
axes[1].set_title('相关性热力图', fontsize=14)  # 设置标题

plt.tight_layout()  # 调整布局
plt.show()  # 显示图形

28.6.2 产品关联分析

列表 28.11
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# =============================================================================
# 题目: 产品共现分析
# =============================================================================
# 本代码块演示如何分析产品之间的关联关系。产品共现分析可以发现哪些
# 产品经常被一起购买,为捆绑销售、推荐系统和货架摆放提供依据。

# ==================== 导入组合函数 ====================
from itertools import combinations  # 导入combinations函数,用于生成组合

# ==================== 获取每个订单的产品列表 ====================
order_products = data_clean.groupby('order_id')['product_name'].apply(list)  # 按订单分组,获取产品列表

# ==================== 统计产品对 ====================
co_occurrence = {}  # 创建空字典,存储产品对的共现次数
for products in order_products:  # 遍历每个订单的产品列表
    for combo in combinations(sorted(products), 2):  # 生成所有2个产品的组合
        co_occurrence[combo] = co_occurrence.get(combo, 0) + 1  # 统计共现次数

# ==================== 转换为DataFrame ====================
co_df = pd.DataFrame(list(co_occurrence.items()), columns=['产品对', '共现次数'])  # 转换为DataFrame
co_df = co_df.sort_values('共现次数', ascending=False).head(10)  # 按共现次数降序排序,取前10

# ==================== 输出结果 ====================
print('Top 10产品组合:')  # 打印标题
print(co_df)  # 打印共现次数最高的10个产品对

# ==================== 可视化 ====================
plt.figure(figsize=(12, 6))  # 创建图形
plt.barh(range(len(co_df)), co_df['共现次数'], color='steelblue', alpha=0.7)  # 绘制水平柱状图
plt.yticks(range(len(co_df)), [f'{p1} + {p2}' for p1, p2 in co_df['产品对']])  # 设置y轴标签
plt.xlabel('共现次数', fontsize=12)  # 设置x轴标签
plt.title('产品共现分析(Top 10)', fontsize=14)  # 设置标题
plt.grid(axis='x', alpha=0.3)  # 显示x轴网格
plt.tight_layout()  # 调整布局
plt.show()  # 显示图形